from rlcore import ProgrammerGoof, Colour, Glyph

from rlkbinput import Key, KeyboardInput, KeyboardInterpreter


class NeedFancierTerminal (Exception):
	def __init__(self, s): self.s = s
	def __str__(self): return self.s
class Interrupted ( Exception ): pass
class TerminalResized ( Exception ): pass

class UnknownTextFormatting (Exception):
	def __init__(self, s): self.s = s
	def __str__(self): return self.s
	def __repr__(self): return self.s

class NoInputHandler (Exception): pass

class LayoutImpossible ( Exception ): pass


class InterfaceElement:
	def __init__(self, interface, name, priority = 0):
		interface.elements[name] = self
		self.priority = priority
		self.name = name
		self.interface = interface
	def move(self, x, y):
		self.xLayout, self.yLayout = x, y
		return self
	def resize(self, w, h):
		self.width, self.height = w, h
		return self
		
class ColourElement (InterfaceElement):
	def __init__(self, interface, name, colour, priority = -1):
		InterfaceElement.__init__( self, interface, name)
		self.colour = colour
	def draw(self):
		for y in range(self.yLayout, self.yLayout + self.height):
			self.interface.move( self.xLayout, y )
			self.interface.putString( " " * self.width, Glyph(' ', self.colour, self.colour ) )
				

class ViewportElement ( InterfaceElement ):
	def __init__(self, interface, name, offset, mobile = None): #ERL swapped last two arguments
		InterfaceElement.__init__( self, interface, name)
		self.mobile = mobile
		self.scroll( offset )
		self.visionMask = None
		self.fovCheat = False
	def scroll(self, offset):
		self.xOffset, self.yOffset = offset
	def draw(self):
		for y in range( 0, self.height ):
			self.interface.move( self.xLayout, self.yLayout + y )
			yp = y + self.yOffset
			for x in range( self.width ):
				xp = x + self.xOffset
				if xp < 0 or xp >= self.mobile.level.width or yp < 0 or yp >= self.mobile.level.height:
					self.interface.putBlank()
				else:
					tile = self.mobile.level.tiles[x+self.xOffset][y+self.yOffset]
					if self.fovCheat or not self.visionMask or self.visionMask.isLit( xp, yp ):
						appearance = tile.appearance()
						self.interface.put( appearance )
					elif tile.remembered:
						# These are EngineRL hacks; do not keep for other games!
						# (They assume truecolour.)
						appearance = tile.appearance( showMobiles = False, showItems = False )
						newFg = tuple( [ c/3 for c in appearance.fgColour ] )
						newBg = tuple( [ c/3 for c in appearance.bgColour ] )
						appearance = appearance.recolorize( newFg, newBg )
						self.interface.put( appearance )
					else:
						self.interface.putBlank()
	def recenter(self, x, y):
		self.scroll( (x - self.width//2, y - self.height//2) )
	def maskVision(self, visionMask):
		self.visionMask = visionMask

class WrappedTextElement (InterfaceElement):
	def __init__(self, interface, name, moreIndicator, fgColour = None, bgColour = None):
		InterfaceElement.__init__(self, interface, name )
		self.text = ""
		self.pages = [ [] ]
		self.moreIndicator = moreIndicator
		if not fgColour: fgColour = Colour.white
		if not bgColour: bgColour = Colour.black
		self.fgColour, self.bgColour = fgColour, bgColour
	def resize(self, w, h):
		self.setDimensions( (w,h) )
	def setDimensions(self, dimensions):
		self.width, self.height = dimensions
		self.reparse()
	def setText(self, parsable):
		self.text = parsable
		self.reparse()
	def reparse(self):
		from rltext import TextWrapper
		self.pages = TextWrapper( self.width, self.height, self.moreIndicator ).parse( self.text )
	def morePages(self):
		return len( self.pages ) > 1
	def nextPage(self):
		self.pages.pop(0)
	def clear(self):
		self.setText( "" )
	def draw(self):
		lines = self.pages[0]
		for i in range(self.height):
			self.interface.move( self.xLayout, self.yLayout + i )
			self.interface.putString( " " * self.width, Glyph(' ', self.fgColour, self.bgColour) )
#			self.interface.putBlanks( self.width )
			self.interface.move( self.xLayout, self.yLayout + i )
			try:
				self.interface.putFormattedText( lines[i], defaultForeground = self.fgColour, defaultBackground = self.bgColour )
			except IndexError:
				pass


class InputHandler:
	def __init__(self, interface):
		self.interface = interface
		self.downstream = self.interface.inputHandlerStream
		self.interface.inputHandlerStream = self
		self.upstream = None
	def quit(self):
		if self.upstream:
			self.upstream.downstream = self.downstream
		else:
			self.interface.inputHandlerStream = self.downstream
		if self.downstream:
			self.downstream.upstream = self.upstream
	def handle(self, event):
		pass

class DisplayTextInputHandler (InputHandler):
	def __init__(self, interface, wte, text = None):
		InputHandler.__init__(self, interface)
		self.wte = wte
		if text != None:
			self.wte.setText(text)
		if not self.wte.morePages(): self.quit()
	def handle(self, event):
		if event.ascii and chr(event.ascii) in [' ']:
			self.wte.nextPage()
			self.interface.refresh()
		if not self.wte.morePages(): self.quit()
		# modality; ignored events are discarded (not sent downstream)

class NotImplementedInSubclass (Exception):
	def __init__(self, s): self.s = s
	def __str__(self): return s
	
class Interface:
	def __init__(self, layout, standardSize = (80,25) ):
		self.width, self.height = None, None # the subclass should keep these current
		self.inputHandlerStream = None
		self.elements = {}
		self.layoutHandler = layout
		self.noLayout = True
		self.standardSize = standardSize
	def move(self, x, y):
		"""Move the cursor to column x, row y, starting from (0,0)."""
		raise NotImplementedInSubclass( "Interface.move not implemented" )
	def put(self, glyph):
		"""Put the glyph on the screen buffer in the cursor position."""
		raise NotImplementedInSubclass( "Interface.put not implemented" )
	def putString(self, text, protoglyph, limit = None):
		"""Put a string styled as a glyph on the screen buffer at the cursor position."""
		i = 0
		for s in text:
			if limit == None or i < limit:
				self.put( Glyph( s, protoglyph.fgColour, protoglyph.bgColour ) )
			i += 1
	def putBlanks(self, n): # is this REALLY needed?
		"""Put a specified number of blanks on the screen buffer at the cursor position."""
		raise NotImplementedInSubclass( "Interface.putBlanks not implemented" )
	def clear(self):
		"""Clear the screen buffer."""
		for line in range(self.height):
			self.move( 0, line )
			self.putBlanks( self.width )
	def refresh(self):
		"""Draw the interface elements to the screen and display them."""
		raise NotImplementedInSubclass( "Interface.refresh not implemented" )
	def getInput(self):
		"""Wait for and return the next input event."""
		raise NotImplementedInSubclass( "Interface.getInput not implemented" )
	def putBlank(self):
		self.putBlanks( n = 1 )
	def putFormattedText(self, formattedText, limit = None, defaultForeground = None, defaultBackground = None):
		if not defaultForeground:
			defaultForeground = Colour.white
		if not defaultBackground:
			defaultBackground = Colour.black
		from rltext import TextFormatting
		from rlcore import Glyph
		tagsInEffect = []
		for symbol in formattedText:
			if isinstance( symbol, TextFormatting ):
				symbol.modify( tagsInEffect )
			else:
				protoGlyph = Glyph( ' ', defaultForeground, defaultBackground )
				isInvisible = False
				for tag in tagsInEffect:
					tf = TextFormatting( tag )
					if tf.fgColour() != None:
						protoGlyph.fgColour = tf.fgColour()
					elif tf.bgColour() != None:
						protoGlyph.bgColour = tf.bgColour()
					elif tf.isInvisible():
						isInvisible = True
					else:
						raise UnknownTextFormatting( tag )
				if isInvisible:
					protoGlyph.fgColour = protoGlyph.bgColour
				self.putString( symbol, protoGlyph, limit)
				if limit: limit -= len(symbol)
	def waitForInput(self):
		if not self.inputHandlerStream:
			raise NoInputHandler()
		event = self.getInput()
		self.inputHandlerStream.handle( event )
	def layout(self):
		"""Accommodate the size of the interface to the screen size."""
		try:
			self.layoutHandler( self )
			self.noLayout = False
		except LayoutImpossible:
			if self.standardSize == (self.width,self.height):
				raise ProgrammerGoof( "layout impossible for standard size %s" % repr(self.standardSize) )
			w, h = self.width, self.height
			self.width, self.height = self.standardSize
			self.layout()
			self.width, self.height = w,h
			self.noLayout = True
	def start(self):
		pass
	def quit(self):
		pass
	def drawElements(self):
		if not self.noLayout:
			elements = sorted( [ (self.elements[key].priority, self.elements[key]) for key in self.elements.keys() ] )
			for score, element in elements:
				element.draw()
	def __deepcopy__(self, memo):
		# interfaces should not be copied.
		return self
